package com.atguigu.srb.core.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.srb.base.util.Asserts;
import com.atguigu.srb.base.util.JwtUtils;
import com.atguigu.srb.common.result.ResponseEnum;
import com.atguigu.srb.core.entity.Lend;
import com.atguigu.srb.core.entity.LendItem;
import com.atguigu.srb.core.entity.TransFlow;
import com.atguigu.srb.core.entity.UserInfo;
import com.atguigu.srb.core.enums.LendStatusEnum;
import com.atguigu.srb.core.enums.TransTypeEnum;
import com.atguigu.srb.core.hfb.FormHelper;
import com.atguigu.srb.core.hfb.HfbConst;
import com.atguigu.srb.core.hfb.RequestHelper;
import com.atguigu.srb.core.mapper.LendItemMapper;
import com.atguigu.srb.core.service.*;
import com.atguigu.srb.core.util.LendNoUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.xml.crypto.Data;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 标的出借记录表 服务实现类
 * </p>
 *
 * @author Atguigu
 * @since 2023-06-30
 */
@Service
@Slf4j
public class LendItemServiceImpl extends ServiceImpl<LendItemMapper, LendItem> implements LendItemService {
    /**
     * 核心的业务:
     * 1.校验参数:
     * 1.1 标的状态 是否满标(投资金额 + 已投金额 不能超过标的总金额),状态是否是募资中
     * 1.2 投资金额不能超过账户的余额
     * 2.保存投资记录 LendItem
     * 3.保存transFlow交易记录
     * 4.构建form表单
     */
    @Resource
    LendService lendService;
    @Resource
    UserAccountService userAccountService;
    @Resource
    UserInfoService userInfoService;
    @Resource
    TransFlowService transFlowService;
    @Override
    public String handleVest(String token, Long lendId, BigDecimal investAmount) {
        //先获取投资人的userId
        Long userId = JwtUtils.getUserId(token);
        UserInfo userInfo = userInfoService.getById(userId);
        //根据lendId查询获取标的对象
        Lend lend = lendService.getById(lendId);
        //1.校验相关的参数
        //1.1校验是否满标 投资金额 + 已投金额 不能超过总的标的的金额
        Asserts.AssertTrue(investAmount.add(lend.getInvestAmount()).compareTo(lend.getAmount()) <= 0,
                ResponseEnum.LEND_FULL_SCALE_ERROR);
        //1.2交易lend的状态必须是募资中(1)  其他状态都不能让其继续往下执行
        Asserts.AssertTrue(lend.getStatus().intValue() == LendStatusEnum.INVEST_RUN.getStatus().intValue(),
                ResponseEnum.LEND_INVEST_ERROR );
        //1.3 投资的金额不能超过账户里面的金额
        Asserts.AssertTrue(investAmount.compareTo(userAccountService.getAmount(userId)) <= 0 ,
                ResponseEnum.LEND_AMOUNT_ERROR);
        //1.4投资的金额不能小于等于0
        Asserts.AssertTrue(investAmount.compareTo(new BigDecimal(0)) > 0,ResponseEnum.LEND_AMOUNT_ERROR);

        //2.保存投资记录
        LendItem lendItem = new LendItem();
        String lendItemNo = LendNoUtils.getLendItemNo();
        lendItem.setLendItemNo(lendItemNo);//TODO 投资编号
        lendItem.setLendId(lendId);//标的Id
        lendItem.setInvestUserId(userId);//投资者的userId
        lendItem.setInvestName(userInfo.getName());//投资人名称
        lendItem.setInvestAmount(investAmount);//投资金额
        lendItem.setLendYearRate(lend.getLendYearRate());//年化利率
        lendItem.setInvestTime(new Date());//投资时间
        lendItem.setLendStartDate(lend.getLendStartDate());//开始日期 也就是起息日
        lendItem.setLendEndDate(lend.getLendEndDate());//结束日期
        lendItem.setExpectAmount(userAccountService.expectAmount(lend.getPeriod(),
                //年化率在expectAmount这个方法里面已经除过100了,所以这里不用再除以100
                lend.getLendYearRate(), lend.getReturnMethod(),investAmount));//预期收益
        lendItem.setRealAmount(new BigDecimal(0));//实际收益
        lendItem.setStatus(0);//状态,默认
        //保存
        this.save(lendItem);

        //3.保存transFlow交易记录 这个地方的保存流水的方法已经提取到TransFlowServiceImpl里面了,这里直接调用即可
        /* TransFlow transFlow = new TransFlow();
        transFlow.setUserId(userId);//用户的id
        transFlow.setUserName(userInfo.getName());//userName
        //TODO 交易单号 使用上面生成的投资编号
        transFlow.setTransNo(lendItemNo);
        transFlow.setTransType(TransTypeEnum.INVEST_LOCK.getTransType());//交易类型
        transFlow.setTransTypeName(TransTypeEnum.INVEST_LOCK.getTransTypeName());//transTypeName交易类型名称
        transFlow.setTransAmount(investAmount);//交易金额
        transFlow.setStatus(0);//status */
        //保存交易流水
        transFlowService.saveTransFlow(investAmount,userInfo,lendItemNo,TransTypeEnum.INVEST_LOCK,0,"");
        //交易备注等到回调的时候再来设置

        //4.构建form表单
        Map<String, Object> params = new HashMap<>();
        params.put("agentId",HfbConst.AGENT_ID);//hfb给商户分配的唯一标识
        params.put("voteBindCode",userInfo.getBindCode());//投资人绑定协议号
        //借款人的bindCode 从lend中获得借款人的userId 根据userId获取bindCode
        Long borrowerUserId = lend.getUserId();
        UserInfo borrowerInfo = userInfoService.getById(borrowerUserId);
        String borrowerBindCode = borrowerInfo.getBindCode();
        params.put("benefitBindCode",borrowerBindCode);
        //TODO 项目编号 对同一个标的投资的多个投资记录通过 标的的编号在hfb中关联
        params.put("agentProjectCode",lend.getLendNo());
        params.put("agentProjectName",lend.getTitle());//项目名称
        //TODO 项目编号 直接使用上面生成的lendItemNo 本次投资的唯一的编号: 可以使用本次投资记录的唯一编号
        params.put("agentBillNo",lendItemNo);
        params.put("voteAmt",investAmount);//投资金额
        params.put("votePrizeAmt",new BigDecimal(0));//投资奖励金额,这里暂时设置为0
        params.put("voteFeeAmt",new BigDecimal(0));//这里也暂时不收手续费
        params.put("projectAmt",lend.getAmount());//项目总金额
        // params.put("note",);//此项非必须可以不用设置
        params.put("notifyUrl",HfbConst.INVEST_NOTIFY_URL);//回调的地址
        params.put("returnUrl",HfbConst.INVEST_RETURN_URL);//投标成功后要返回的地址
        params.put("timestamp",System.currentTimeMillis());//时间戳
        //设置签名
        String sign = RequestHelper.getSign(params);
        params.put("sign",sign);

        return FormHelper.buildForm(HfbConst.INVEST_URL, params);
    }

    //s = {"agentBillNo":"INVEST20230719204729084",
    // "agentProjectCode":"LEND20230717232749381",
    // "projectType":"0","resultCode":"0001","resultMsg":"充值成功",
    // "sign":"f33f164cb1f53afddf20f7833ab209a4",
    // "timestamp":"1689770857377",
    // "voteAmt":"5000","voteBindCode":"1d02a72e0cf1466e9cc3414d7f63338f",
    // "voteFeeAmt":"0","voteNote":"","votePrizeAmt":"0"}
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String handleCallback(Map<String, Object> map) {
        //根据agentBillNo可以查出来transFlow
        String agentBillNo = map.get("agentBillNo").toString();
        TransFlow transFlow = transFlowService.getByTransNo(agentBillNo);
        //充值交易状态0 未完成; 1 已完成; 2 失败; 3 resultCode异常; 4 代表验签失败; 5 代表充值金额异常
        //1.校验参数
        //1.1校验幂等性
        //1.1.1校验transFlow的status是否存在,如果不存在说明没有经过上一步的投标方法,直接对我们的callback接口发起的请求,非法的请求
        if(transFlow == null){
            //未经正常的途径发起的请求,非法的
            //打印日志
            log.error("请求异常,参数为:{}", JSON.toJSONString(map));
            //不用修改transFlow的状态了
            //结束方法的执行
            return "fail";
        }
        //1.1.2如果transFlow的状态不为0,说明已经不是第一次进来这个callback方法了,拒绝往下执行
        if(transFlow.getStatus() != 0){
            //日志也不用打印了
            // log.error("请求异常,参数为:{}", JSON.toJSONString(map));
            //不用修改transFlow的状态了
            //结束方法的执行 有可能是已完成的,需要再判断下
            return transFlow.getStatus() == 1 ? "success" : "fail";
        }
        //1.3验签
        if (!RequestHelper.isSignEquals(map)) {
            //进来这里面说明签名异常
            //打印日志
            log.error("验签失败,hfb投标后回调的参数为:{}", JSON.toJSONString(map));
            //修改transFlow的状态
            transFlow.setStatus(4);//验签失败
            //更新transFlow
            transFlowService.updateById(transFlow);
            //结束方法的执行
            return "fail";
        }
        //1.4验证投标的金额是否一致
        //从回调的参数中获取投标的金额
        BigDecimal voteAmt = new BigDecimal(map.get("voteAmt").toString());
        //从transFlow里面获取交易的金额
        BigDecimal transAmount = transFlow.getTransAmount();
        if(voteAmt.compareTo(transAmount) != 0){
            //进来这里面说明交易的金额不一致,有问题
            //打印日志
            log.error("投标金额匹配失败,hfb投标后回调的参数为:{}", JSON.toJSONString(map));
            //修改transFlow的状态
            transFlow.setStatus(5);//金额不匹配
            transFlow.setMemo(JSON.toJSONString(map));
            //更新transFlow
            transFlowService.updateById(transFlow);
            //结束方法的执行
            return "fail";
        }
        //1.5校验响应参数里面的resultCode是否ok
        String resultCode = map.get("resultCode").toString();
        if(!StringUtils.equals(resultCode,"0001") || !map.containsKey("resultCode")){
            //resultCode不为0001,或者响应的参数里面压根就没有resultCode,说明失败
            //打印日志
            log.error("resultCode匹配失败,hfb投标后回调的参数为:{}", JSON.toJSONString(map));
            //修改transFlow的状态
            transFlow.setStatus(3);//resultCode不匹配
            //更新transFlow
            transFlowService.updateById(transFlow);
            //结束方法的执行
            return "fail";
        }
        //2.下面的就是更新用户的amount和freezeAmount了
        //根据响应里面的voteBindCode(需要先查出来userId)更新amount和freezeAmount
        String voteBindCode = map.get("voteBindCode").toString();
        Long userId = transFlow.getUserId();
        Integer integer = userAccountService.updateAmountAndFreezeAmountByUserId(transFlow.getTransAmount().negate(),transFlow.getTransAmount(),userId,new Date());
        //TODO 测试一下接收返回的受影响的行数能不能成功
        System.out.println("integer = " + integer);

        //4.更新LendItem的状态为1（0：默认 1：已支付 2：已还款）
        //根据响应里面的agentBillNo(其实也就是lendItemNo),可以唯一的确认lendItem
        LendItem lendItem = this.getOne(Wrappers.lambdaQuery(LendItem.class).eq(LendItem::getLendItemNo, agentBillNo));
        /* this.update(Wrappers.lambdaUpdate(LendItem.class).eq(LendItem::getLendItemNo,agentBillNo)
                .set(LendItem::getStatus,1)
                //时间填充器不生效,需要我们自己手动的传入一个日期,不要使用数据库自己默认生成的日期,有可能格式或者时区不正确
                .set(LendItem::getUpdateTime,new Date())); */
        lendItem.setStatus(1);
        this.updateById(lendItem);

        //3.更新投资的人数和已投资的金额
        //3.1根据响应里面的agentProjectCode(其实就是lendNo)可以找到对应的lend对象
        String agentProjectCode = map.get("agentProjectCode").toString();
        // Lend lend = lendService.getOne(Wrappers.lambdaQuery(Lend.class).eq(Lend::getLendNo, agentProjectCode));
        //还可以根据lendItem对象获取里面的lendId
        Lend lend = lendService.getById(lendItem.getLendId());
        //3.2设置已投资的人数
        lend.setInvestNum(lend.getInvestNum() + 1);
        //3.3设置已投资的金额
        lend.setInvestAmount(lend.getInvestAmount().add(transFlow.getTransAmount()));
        //3.4更新lend对象
        lendService.updateById(lend);


        //5.更新transFlow的状态为已完成 1
        transFlow.setStatus(1);
        //设置一下transFlow的memo备注
        transFlow.setMemo(JSON.toJSONString(map));
        transFlowService.updateById(transFlow);

        return "success";
    }
    //获取所有投资人的id的方法

    /**
     *
     * @param lendId 标的id
     * @param status 标的的状态 1为已支付
     * @return 所有投资者的userID
     */
    @Override
    public List<Long> getInvestorUserId(Long lendId,Integer status){
        //根据标的id和标的状态获取到的所有符合要求的投标信息
        List<LendItem> lendItem = this.list(Wrappers.lambdaQuery(LendItem.class).eq(LendItem::getId, lendId).eq(LendItem::getStatus, status));
        //遍历list,取出里面的所有的投资者的id组成新的list并返回
        return lendItem.stream().map(LendItem::getInvestUserId).collect(Collectors.toList());
    }
}
